var express = require("express");
var request = require("sync-request");
var url = require("url");
var qs = require("qs");
var querystring = require('querystring');
var cons = require('consolidate');
var randomstring = require("randomstring");
var __ = require('underscore');
__.string = require('underscore.string');

//创建app服务器
var app = express();

//设置渲染引擎
app.engine('html', cons.underscore);
app.set('view engine', 'html');
app.set('views', 'files/client');

//授权服务器的两个Endpoint
var authServer = {
	authorizationEndpoint: 'http://localhost:9001/authorize',
	tokenEndpoint: 'http://localhost:9001/token'
};


var client = {
	"client_id":"oauth-client-1",
	"client_secret":"oauth-client-secret-1",
	"redirect_uris": ["http://localhost:9000/callback"],
	"scope":"foo"
}

//被保护资源地址
var protectedResource = 'http://localhost:9002/resource';

var access_token = null
var scope = null
var refresh_token = null;
var state = null

//Client也是一个服务器,这是它相应给我们的主页信息,显示目前Client上保存的access_token和scope,默认为空
app.get('/',function(req,res){
	res.render('index',{access_token: access_token,scope: scope,refresh_token: refresh_token})
});

//在页面上点击按钮'Get OAuth Token',浏览器就会请求此地址,开始client申请token的流程
app.get('/authorize',function(req,res){
	
	//请求新的token之前,先清空现存的token
	access_token = null
	scope = null
	state = randomstring.generate()
	
	var authorizeUrl=buildUrl(authServer.authorizationEndpoint,{
		response_type: 'code',
		scope: client.scope,
		client_id: client.client_id,
		redirect_uri: client.redirect_uris[0],
		state:state
		
	})
	console.log("redirect", authorizeUrl);
	res.redirect(authorizeUrl)
});

//授权服务器授权完成后,会把浏览器重定向到该地址,这时client应该携带请求过来的code去认证服务器换取token
app.get('/callback',function(req,res){
	
	//授权确认认证服务器返回的结果不是error
	if(req.query.error){
		res.render('error',{error:req.query.error})
		return
	}
	
	//state处理
	var resState = req.query.state;
	if (resState != state) {
		console.log('State DOES NOT MATCH: expected %s got %s', state, resState);
		res.render('error', {error: 'State value did not match'});
		return;
	}
	
	//取得浏览器重定向携带来的授权码
	var code = req.query.code;
	//构建请求token的header信息
	var headers = {
		'Content-Type': 'application/x-www-form-urlencoded',
		'Authorization': 'Basic ' + encodeClientCredentials(client.client_id, client.client_secret)
	}
	//构建请求token的body信息
	var form_data = qs.stringify({
		//请求token的时候,需要再次发送授权类型
		grant_type: 'authorization_code',
		//授权码
		code: code,
		redirect_uri: client.redirect_uris[0]
	})
	
	//请求token
	var tokRes=request('POST',authServer.tokenEndpoint,{
			body:form_data,
			headers:headers
		},
	)
	console.log('Requesting access token for code %s',code);
	
	//取得token后的处理
	//收到授权服务器的正确答复
	if(tokRes.statusCode >=200 && tokRes.statusCode <300){
		var body = JSON.parse(tokRes.getBody())
		access_token = body.access_token
		console.log('Got access token: %s', access_token);
		//refresh_token
		if (body.refresh_token) {
			refresh_token = body.refresh_token;
			console.log('Got refresh token: %s', refresh_token);
		}
		scope = body.scope
		console.log('Got scope: %s', scope);
		
		//处理完毕,回复主页重定向给浏览器
		res.render('index',{access_token: access_token, scope: scope, refresh_token: refresh_token})
		
	}else{
		res.render('error', {error: 'Unable to fetch access token, server response: ' + tokRes.statusCode})
	}	
})

//用户在浏览器点击按钮Get Protected Resource, 请求Client的地址/fetch_resource
//client应该携带已经获得的token去ResourceServer请求资源,请求到资源以后,返回给浏览器
app.get('/fetch_resource',function(req,res){
	
	console.log('Making request with access token %s', access_token);
	
	//构建请求信息
	//注意请求受保护资源时的头部格式
	var headers = {
		'Authorization': 'Bearer ' + access_token,
		'Content-Type': 'application/x-www-form-urlencoded'
	}
	
	//发起请求
	var resource = request('POST', protectedResource,
		//只有头部信息的POST
		{headers: headers}
	);
	
	//对请求结果的处理
	//结果正常
	if(resource.statusCode >=200 && resource.statusCode <300){
		var body = JSON.parse(resource.getBody())
		res.render('data',{resource:body})
		return
	}else{
		//返回结果一场,说明access_token有问题,先清空当前access_token
		access_token = null
		//尝试用refresh_token去获取新的token
		//在实际应用中,access_token和refresh_token一定是保存在数据库中的,而不这样只能只有一个可用.
		if(refresh_token){
			refreshAccessToken(req, res);
			return
		}else{
			//access_token不正常,也没有refresh_token,返回异常
			res.render('error', {error: resource.statusCode});
			return;
		}	
	}	
})

//refresh_token换取token的过程
var refreshAccessToken = function(req,res){
	
	//构建请求信息
	var form_data = {
		//注意,这里的grant_type换成了refresh_token
		grant_type: 'refresh_token',
		//对比上边,这里的授权码换成了refresh_token,同时也不需要在附加重定向地址
		refresh_token: refresh_token
	}
	
	var headers = {
		'Content-Type': 'application/x-www-form-urlencoded',
		'Authorization': 'Basic ' + encodeClientCredentials(client.client_id, client.client_secret)
	}
	
	console.log('Refreshing token %s', refresh_token);
	//发起请求
	var tokRes = request('POST',authServer.authorizationEndpoint,
		{
			body:form_data,
			headers:headeres
		}
	)
	if(tokRes.statusCode >=200 && tokRes.statusCode <300){
		var body = JSON.parse(tokRes.getBody())
		access_token = body.access_token
		console.log('Got access token: %s', access_token);
		//如果返回的token中携带了refresh_token,则刷新当前的refresh_token
		if(body.refresh_token){
			refresh_token = body.refresh_token
			console.log('Got refresh token: %s', refresh_token);
		}
		scope = body.scope
		console.log('Got scope: %s', scope);
		
		//token刷新好了,让浏览器重新请求一次,流程再走一遍
		res.redirect('/fetch_resource');
		return;	
	}else{
		console.log('No refresh token, asking the user to get a new access token');
		// tell the user to get a new access token
		refresh_token = null;
		res.render('error', {error: 'Unable to refresh token.'});
		return;
	}
}

//URL构建方法
var buildUrl = function(base, options, hash) {
	var newUrl = url.parse(base, true);
	delete newUrl.search;
	if (!newUrl.query) {
		newUrl.query = {};
	}
	__.each(options, function(value, key, list) {
		newUrl.query[key] = value;
	});
	if (hash) {
		newUrl.hash = hash;
	}
	
	return url.format(newUrl);
};


var encodeClientCredentials = function(clientId, clientSecret) {
	return new Buffer(querystring.escape(clientId) + ':' + querystring.escape(clientSecret)).toString('base64');
};

//设置静态资源
app.use('/', express.static('files/client'));


//启动服务器
var server = app.listen(9000, 'localhost', function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('OAuth Client is listening at http://%s:%s', host, port);
});